package com.platform.common.utils;

import cn.binarywang.wx.miniapp.api.WxMaService;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.coupon.WxPayCouponSendRequest;
import com.github.binarywang.wxpay.service.WxPayService;
import com.platform.common.exception.BusinessException;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.net.ssl.SSLContext;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;


@Slf4j
@Component
public class SendCoupon {
    @Autowired
    private WxMaService wxMaService;

    @Autowired
    private WxMpService wxMpService;

    private CloseableHttpClient httpClient;
    @Value("${wx.pay.mchSerialNo}")
    String mchSerialNo;
    @Value("${wx.pay.mchId}")
    String mchId;
    @Value("${wx.pay.mchKey}")
    String mchKey;
    private static final String STOCK_CREATOR_MCHID = "1111111";

    /**
     * 发送代金券
     *
     * @throws KeyStoreException
     * @throws IOException
     * @throws CertificateException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws KeyManagementException
     * @throws DocumentException
     */
    public static Map sendCoupon(WxPayCouponSendRequest request, WxPayService wxPayService) throws KeyStoreException, NoSuchAlgorithmException
            , CertificateException, IOException, KeyManagementException, UnrecoverableKeyException, DocumentException {
//把请求参数打包成数组
        Map<String, String> sParaTemp = new HashMap<String, String>();
        sParaTemp.put("stock_id", request.getCouponStockId());
        sParaTemp.put("appid", request.getAppid());
        sParaTemp.put("out_request_no", request.getPartnerTradeNo());
        sParaTemp.put("stock_creator_mchid", request.getMchId());
//        sParaTemp.put("openid_count", "1");
//        sParaTemp.put("openid", request.getOpenid());
//        sParaTemp.put("mch_id", request.getMchId());
//        sParaTemp.put("nonce_str", request.getNonceStr());

        //除去数组中的空值和签名参数
        Map<String, String> sPara = paraFilter(sParaTemp);
        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        String prestr = createLinkString(sPara);
        log.info("prestr" + prestr);
        String key = "&key=" + wxPayService.getConfig().getMchKey(); //商户支付密钥
        log.info("prestr + key" + prestr + key);
        String mysign = sign(prestr, key, "utf-8").toUpperCase();
        log.info("mysign" + mysign);
        request.setSign(mysign);
        String respXml = request.toXML();
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        //此处为证书所放的绝对路径
        FileInputStream instream = new FileInputStream(new File(wxPayService.getConfig().getKeyPath()));

        try {
            keyStore.load(instream, wxPayService.getConfig().getMchId().toCharArray());
        } finally {
            instream.close();
        }

        log.info(respXml);
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, wxPayService.getConfig().getMchId().toCharArray())
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[]{"TLSv1"},
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();

        try {
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/marketing/favor/users/" + request.getOpenid() + "/coupons");
            StringEntity reqEntity = new StringEntity(respXml, "utf-8");
            // 设置类型
            reqEntity.setContentType("application/x-www-form-urlencoded");
            httpPost.setEntity(reqEntity);
            CloseableHttpResponse response = httpclient.execute(httpPost);
            try {
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    // 从request中取得输入流
                    InputStream inputStream = entity.getContent();
                    // 读取输入流
                    SAXReader reader = new SAXReader();
                    Document document = reader.read(inputStream);
                    // 得到xml根元素
                    Element root = document.getRootElement();
                    // 得到根元素的所有子节点
                    List<Element> elementList = root.elements();

                    // 遍历所有子节点
                    for (Element e : elementList) {
                        map.put(e.getName(), e.getText());
                    }

                    // 释放资源
                    inputStream.close();
                    inputStream = null;
                }
                EntityUtils.consume(entity);
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
        /**
         * 根据以上返回码进行业务逻辑处理
         */
        return map;
    }


    /**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, String> paraFilter(Map<String, String> sArray) {
        Map<String, String> result = new HashMap<String, String>();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }

        for (String key : sArray.keySet()) {
            String value = sArray.get(key);
            if (value == null || "".equals(value) || "sign".equalsIgnoreCase(key)
                    || "sign_type".equalsIgnoreCase(key)) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {
        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        StringBuilder prestr = new StringBuilder();

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);

            if (i == keys.size() - 1) {//拼接时，不包括最后一个&字符
                prestr.append(key + "=" + value);
            } else {
                prestr.append(key + "=" + value + "&");
            }
        }

        return prestr.toString();
    }

    /**
     * 签名字符串
     *
     * @param text          需要签名的字符串
     * @param key           密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
        return DigestUtils.md5Hex(getContentBytes(text + key, input_charset));
    }

    /**
     * 签名字符串
     *
     * @param text          需要签名的字符串
     * @param sign          签名结果
     * @param key           密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
        text = text + key;
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
        if (mysign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws SignatureException
     * @throws UnsupportedEncodingException
     */
    private static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }


    public void setup(String proxyIp,Integer proxyPort) throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException {
        log.debug(mchKey);
        log.debug(mchId);
        log.debug(mchSerialNo);
        // 加载商户私钥（privateKey：私钥字符串）
        PrivateKey merchantPrivateKey = getPrivateKey("/home/zhengshu/prod/apiclient_key.pem");
        // /home/zhengshu/prod/apiclient_key.pem
        HttpHost httpHost = null;
        if(StringUtils.isNotBlank(proxyIp)){
            httpHost = new HttpHost(proxyIp, proxyPort);
        }
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 添加代理服务器
        //certificatesManager.setProxy(httpHost);
        // 加载平台证书（mchId：商户号,mchSerialNo：7134F8A9EB3854BEC89AB455C8CA79ED51337B7B 商户证书序列号,
        // apiV3Key：V3密钥）

        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                        new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
                mchKey.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);
        // 初始化httpClient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
//                .setProxy(httpHost)
                .build();
    }

    public void after() throws IOException {
        httpClient.close();
    }


    public JSONObject sendCouponV3(JSONObject jsonObject,String proxyIp,Integer proxyPort) throws Exception {
        setup(proxyIp,proxyPort);
        //请求URL
        HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/marketing/favor/users/"
                + jsonObject.getString("openid") + "/coupons");
        String out_request_no = mchId + StringUtils.generateOrderNumber();
        jsonObject.put("outRequestNo", out_request_no);
        // 请求body参数
        String reqdata = "{"
                + "\"stock_id\":\"" + jsonObject.getString("stockId") + "\","
                + "\"out_request_no\":\"" + out_request_no + "\","
                + "\"appid\":\"" + wxMpService.getWxMpConfigStorage().getAppId() + "\","
                + "\"stock_creator_mchid\":\"" + STOCK_CREATOR_MCHID + "\""
                + "}";
        StringEntity entity = new StringEntity(reqdata, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        log.info("reqdata = " + reqdata);
        log.info("reqdata = " + httpPost.getURI());
        //完成签名并执行请求
        CloseableHttpResponse response = httpClient.execute(httpPost);

        try {
            int statusCode = response.getStatusLine().getStatusCode();
            jsonObject.put("statusCode", statusCode);
            String body = EntityUtils.toString(response.getEntity());
            if (statusCode == 200) { //处理成功
                log.info("success,return body = " + body);
                jsonObject.put("redata", body + ",statusCode:" + statusCode);
                return jsonObject;
            } else if (statusCode == 204) { //处理成功，无返回Body
                System.out.println("success");
                jsonObject.put("redata", ",statusCode:" + statusCode);
                return jsonObject;
            } else {
                log.info("failed,resp code = " + statusCode + ",return body = " + body);
                jsonObject.put("redata", body + ",statusCode:" + statusCode);
//                return jsonObject;
                throw new BusinessException("立减金发放失败:" + body);
            }
        } finally {
            after();
            response.close();
        }
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {

        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
}
